﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;

namespace TextProject
{
	public partial class FormattedTextBox : UserControl
	{
		private FormattedText text = new FormattedText();
		private List<LineSize> lines = new List<LineSize>();
		private List<List<int>> lineSet = new List<List<int>>();
		float totalTextHeight;

		Graphics grUtil;

		StringFormat stringFormat = new StringFormat(StringFormat.GenericTypographic);

		public int SelectionStart { get; set; }
		public int SelectionLength { get; set; }
		public bool NeedRecalc { get; set; }

		public new FormattedText Text
		{
			get
			{
				return text;
			}
			set
			{
				text = value;
				CalculateCharacterRects();
				ArrangeTextBoxLines();
				Invalidate();
			}
		}

		private void CalculateCharacterRects()
		{
			int length = text.Length;
			float lineSpace;
			float ascent;
			float baseline;

			int maxTextWidth = ClientSize.Width - vScrollBar1.Width;

			for (int i = 0; i < length; i++)
			{
				var desc = text[i];

				Style style = desc.CharStyle;

                if (!style.fontArea.ContainsKey(desc.Character))
                {
                    lineSpace = style.Font.FontFamily.GetLineSpacing(style.Font.Style);
                    ascent = style.Font.FontFamily.GetCellAscent(style.Font.Style);
                    baseline = style.Font.GetHeight(grUtil) * ascent / lineSpace;

					var fontSize = grUtil.MeasureString(desc.Character.ToString(), style.Font, maxTextWidth, stringFormat);

                    style.fontArea.Add(desc.Character, new Area(0, 0 - baseline, fontSize.Width, fontSize.Height));
                }				
			}
		}

		public FormattedTextBox()
		{
			InitializeComponent();

			MouseWheel += new MouseEventHandler(vScrollBar1_MouseWheel);
			//MouseMove += new MouseEventHandler(vScrollBar1_MouseWheel);

			stringFormat.Trimming = StringTrimming.None;
			stringFormat.FormatFlags = StringFormatFlags.MeasureTrailingSpaces | StringFormatFlags.FitBlackBox;
			float[] tabStops = { 150.0f, 100.0f, 100.0f };
			stringFormat.SetTabStops(0.0f, tabStops);

			SelectionStart = 0;
			SelectionLength = 0;
			totalTextHeight = 0;

			grUtil = CreateGraphics();
		}

		void vScrollBar1_MouseWheel(object sender, MouseEventArgs e)
		{
			int newValue = vScrollBar1.Value - vScrollBar1.SmallChange * e.Delta / 120;
			
			if (newValue < 0)
			{
				newValue = 0;
			}
			
			if (newValue > vScrollBar1.Maximum)
			{
				newValue = vScrollBar1.Maximum;
			}

			vScrollBar1.Value = newValue;
			Invalidate();
		}

		private void AdjustLineSizeTo(LineSize line, Area area)
		{
			int lastIdx = lines.Count - 1;

			if (line.height < area.height)
			{
				line.height = area.height;
			}

			if (line.ascent > area.y)
			{
				line.ascent = area.y;
			}

			line.width += area.width;
		}

		private void CreateLine(int startIndex, int endIndex)
		{
			var line = new LineSize();
			var idxList = new List<int>();

			for (int i = startIndex; i < endIndex; i++)
			{
				AdjustLineSizeTo(line, text[i].GetArea());
				idxList.Add(i);
			}
			
			totalTextHeight += line.height;

			lines.Add(line);
			lineSet.Add(idxList);
		}

		public void ArrangeTextBoxLines()
		{
			////////////////Line Class needed/////////////////////////////////////////
			lines.Clear();
			lineSet.Clear();
			totalTextHeight = 0;
			//////////////////////////////////////////////////////////////////////////

			int width = ClientSize.Width - vScrollBar1.Width;
			int lineStart = 0;
			int lastSpace = -1;
			float x = 0;

			int idx = 0;
			int length = text.Length;
            CharacterDescriptor[] vec = text.CharList;
			while(idx < length)
			{
				if (vec[idx].GetArea().width < width)
				{
					if (x + vec[idx].GetArea().width >= width)
					{
						if (lastSpace >= lineStart) // word wrap
						{
							CreateLine(lineStart, lastSpace + 1);

							lineStart = lastSpace + 1;
							idx = lineStart;
						}
						else
						{
							CreateLine(lineStart, idx);

							lineStart = idx;
						}
						x = 0;
					}
				}

				if ('\n' == vec[idx].Character) // jump to new line
				{
					CreateLine(lineStart, idx + 1);

					lineStart = idx + 1;
					lastSpace = idx;
					x = 0;
				}

				if (' ' == vec[idx].Character)
				{
					lastSpace = idx;
				}

				x += vec[idx].GetArea().width;
				idx++;
			}

			CreateLine(lineStart, idx);

			int scrollHeight = (int)totalTextHeight - ClientSize.Height;

			vScrollBar1.Visible = scrollHeight > 0;

			if (vScrollBar1.Visible)
			{
				vScrollBar1.SmallChange = (int)totalTextHeight / 20;
				vScrollBar1.LargeChange = (int)totalTextHeight / 10;
				vScrollBar1.Maximum = -1 + (int)scrollHeight + vScrollBar1.LargeChange;
			}			
		}

		public void Draw(Graphics gr)
		{
			if (lines.Count > 0)
			{
				float Y = - lines[0].ascent;

				if (vScrollBar1.Visible)
				{
					Y -= vScrollBar1.Value;
				}

				int lineIdx = 0;
				foreach (List<int> line in lineSet)
				{
					float X = 0;
					foreach (int idx in line)
					{
						var style = text[idx].CharStyle;
						var r = text[idx].GetArea();

						float rx = r.x + X;
						float ry = r.y + Y;

						if (rx <= ClientSize.Width && ry <= ClientSize.Height &&
							rx + r.width >= 0 && ry + r.height >= 0)
						{
							//gr.FillRectangle(style.BackBrush, rx, ry, r.width, r.height);
							gr.DrawString(text[idx].Character.ToString(), style.Font, style.ForeBrush, rx, ry, stringFormat);
						}

						X += r.width;
					}

					Y += lines[lineIdx].height;
					lineIdx++;
				}
			}
		}

		protected override void OnPaint(PaintEventArgs e)
		{	
			Draw(e.Graphics);			
			base.OnPaint(e);
		}
                
		[DllImport("User32.dll")]
		private static extern bool CreateCaret(IntPtr hWnd, int hBitmap, int nWidth, int nHeight);

		[DllImport("User32.dll")]
		private static extern bool SetCaretPos(int x, int y);

		[DllImport("User32.dll")]
		private static extern bool DestroyCaret();

		[DllImport("User32.dll")]
		private static extern bool ShowCaret(IntPtr hWnd);

		[DllImport("User32.dll")]
		private static extern bool HideCaret(IntPtr hWnd);
				
		private void FormattedTextBox_Resize(object sender, EventArgs e)
		{
            DateTime oldDate = DateTime.Now;
			ArrangeTextBoxLines();
            DateTime newDate = DateTime.Now;

            TimeSpan ts = newDate - oldDate;

            System.Diagnostics.Debug.Print("Resize time: {0}", ts.Milliseconds);
		}

		private void vScrollBar1_Scroll(object sender, ScrollEventArgs e)
		{
			Invalidate();
		}
	}
}
